package cn.jy.ad.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;


@Component
@Slf4j
public class OssUtils {
    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;

    @Value("${aliyun.oss.accessKeySecret}")
    private String secretAccessKey;

    @Value("${aliyun.oss.endpoint}")
    private String endPoint;

    @Value("${aliyun.oss.bucketName}")
    private String bucketName;

    public String uploadOneFile(MultipartFile file) {
        log.info("文件上传开始 " + new DateTime());

        // 定义文件大小限制为200MB
        final long maxSize = 200 * 1024 * 1024L;
        final long minSize = 10 * 1024 * 1024L;

        if (file.getSize() > maxSize) {
            log.error("File size exceeds 200MB limit.");
            return null;
        }

        // 创建OSSClient实例
        OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, secretAccessKey);

        /*较小文件直接上传*/
        if (file.getSize() <= minSize) {
            try {
                String fileName = file.getOriginalFilename();
                PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, file.getInputStream());
                ossClient.putObject(putObjectRequest);
                String url = "http://" + bucketName + "." + endPoint + "/" + fileName;
                return url;
            } catch (Exception e) {
                return null;
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
        }

        /* 较大文件分片上传 */
        if (minSize < file.getSize() && file.getSize() <= maxSize) {
            InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName, file.getOriginalFilename());
            InitiateMultipartUploadResult initiateMultipartUploadResult = ossClient.initiateMultipartUpload(initiateMultipartUploadRequest);
            String uploadId = initiateMultipartUploadResult.getUploadId();
            log.info("uploadId: " + uploadId + new DateTime());

            List<PartETag> partETags = Collections.synchronizedList(new ArrayList<>());
            ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

            try {
                final long partSize = 10 * 1024 * 1024L; // 每个分片大小设置为10MB
                long fileLength = file.getSize();
                int partCount = (int) Math.ceil((double) fileLength / partSize);

                List<CompletableFuture<Void>> futures = new ArrayList<>();

                for (int i = 0; i < partCount; i++) {
                    final int partIndex = i;
                    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                        long startPos = partIndex * partSize;
                        long curPartSize = Math.min(partSize, fileLength - startPos);

                        try (InputStream inputStream = file.getInputStream()) {
                            inputStream.skip(startPos);
                            UploadPartRequest uploadPartRequest = new UploadPartRequest(bucketName, file.getOriginalFilename(), uploadId, partIndex + 1, inputStream, curPartSize);
                            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);

                            partETags.add(uploadPartResult.getPartETag());
                        } catch (Exception e) {
                            log.error("Failed to upload part " + (partIndex + 1) + ": " + e.getMessage(), e);
                        }
                    }, executorService);

                    futures.add(future);
                }

                log.info("开始上传分片，分片数量为：" + partCount + new DateTime());
                CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

                log.info("开始合并分片，分片数量为：" + partCount + new DateTime());
                CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, file.getOriginalFilename(), uploadId, partETags);
                ossClient.completeMultipartUpload(completeMultipartUploadRequest);

                String url = "http://" + bucketName + "." + endPoint + "/" + file.getOriginalFilename();
                log.info("文件上传结束 " + new DateTime());
                return url;

            } catch (Exception e) {
                log.error("文件上传失败: " + e.getMessage(), e);
                // 中止上传
                try {
                    ossClient.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, file.getOriginalFilename(), uploadId));
                } catch (Exception abortException) {
                    log.error("中止上传失败: " + abortException.getMessage(), abortException);
                }
                return null;
            } finally {
                executorService.shutdown(); // 关闭线程池
                if (ossClient != null) {
                    ossClient.shutdown(); // 确保 OSS 客户端关闭
                }
            }
        }

        /*
        * 优化要点

            增大分片大小：将每个分片的大小增加到 10MB，这样可以减少分片的总数，从而降低上传的总时间。

            使用输入流进行分片上传：

            使用 file.getInputStream()在上传过程中直接从输入流中读取数据，而不是先将整个文件读入内存。这种方式能有效节省内存空间，并提高处理效率。

            通过调用 inputStream.skip(startPos) 来跳过已经上传的部分，更加高效。

            同步集合：使用 Collections.synchronizedList 来确保 partETags 的线程安全，避免显式的同步块。

            异常处理：在每个分片上传的 runAsync 方法中添加了异常捕获，确保即使一个分片上传失败，也不会影响其他分片的上传。

            日志记录：保留有用的日志信息，可以更好地调试和跟踪上传过程。

            清理资源：在 finally 块中关闭线程池和 OSS 客户端，以确保资源得到合理释放。
        */
        return null;
    }


    public List<String> uploadArrayFile(MultipartFile[] files) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, secretAccessKey);
        List<String> list = new ArrayList<>();

        try {
            for (MultipartFile file : files) {
                // 设置文件名
                String fileName = new DateTime().toString("yyyy/MM/dd")
                        + UUID.randomUUID().toString().replace("-", "")
                        + file.getOriginalFilename();

                // 创建PutObject请求。
                ossClient.putObject(bucketName, fileName, file.getInputStream());

                String url = "http://" + bucketName + "." + endPoint + "/" + fileName;
                list.add(url);
            }

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return list;
    }

    public boolean deleteFile(String fileUrl) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, secretAccessKey);

        int begin = ("http://" + bucketName + "." + endPoint + "/").length(); // 找到文件路径的开始下标
        String deleteUrl = fileUrl.substring(begin);

        try {
            // 删除文件请求
            ossClient.deleteObject(bucketName, deleteUrl);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}
